//
//  Httpd.m
//  aPlanner
//
//  Created by Daniel Braun on 01/09/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "Httpd.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#import "Httpd.h"
#import "NSURLextensions.h"

@interface HttpdConnnection: NSObject <HttpdInformation> {
	NSFileHandle *soc;
	CFHTTPMessageRef httpmsg;
	NSObject<HttpdDelegate> *delegate;
	NSString *peer;
	unsigned short peerPort;
	NSString *browser;
	NSString *method;

}



- (id) initWithFileHandle:(NSFileHandle *)f 
		   delegate:(id)delegate peer:(NSString *)pr 
		 peerPort:(unsigned short)po;

- (void) dataReceived:(NSNotification *)aNotification;

- (void) processUrl:(NSURL*) url; // for:(browser_info_t *)browserinfo;
//- (browser_info_t) parseBrowser:(NSString *)browser accepting:(NSString *)accept;

@end

@implementation HttpdConnnection 

- (void) dealloc {
	[delegate release];
	[soc release];
	[peer release];
	[browser release];
	[method release];
	[super dealloc];
}

- (id) initWithFileHandle:(NSFileHandle *)f delegate:(id)p peer:(NSString *)pr peerPort:(unsigned short)po
{
	self = [super init];
	if (self != nil) {
		delegate=[p retain];
		soc=[f retain];
		peer=[pr retain];
		peerPort=po;
		NSLog(@"%@: new cnx from %s\n", [NSDate date], [peer cString]);
		httpmsg=CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
	}
	return self;
}

- (void) dataReceived:(NSNotification *)aNotification 
{
	NSData *data;
	//browser_info_t bi;
	NSURL *url;
	NSString *braccept;
	data=[[aNotification userInfo] objectForKey:
		NSFileHandleNotificationDataItem];
	if (!data) {
		printf("no data\n");
		return;
	}
	
	//printf("%d data bytes\n", [data length]);
	//NSString *mData=[NSString stringWithUTF8String:[ data bytes]];
	//printf("got httpd data %s\n", [mData description]);
	if (![data length]) return;
	if (!CFHTTPMessageAppendBytes(httpmsg, [data bytes], [data length])) {
		printf("ERROR in CFHTTPMessageAppendBytes\n");
	}
	if (CFHTTPMessageIsHeaderComplete(httpmsg)) {
		[peer release];
		[browser release];
		[method release];
		url=(NSURL *)CFHTTPMessageCopyRequestURL(httpmsg);
		method=(NSString *)CFHTTPMessageCopyRequestMethod(httpmsg);
		browser=(NSString *)CFHTTPMessageCopyHeaderFieldValue(httpmsg, (CFStringRef)@"User-Agent");
		braccept=(NSString *)CFHTTPMessageCopyHeaderFieldValue(httpmsg, (CFStringRef)@"Accept");
		CFRelease(httpmsg); httpmsg=NULL;
		//printf("method %s browser %s\n", [[method description] cString], [[browser description] cString]);
		//bi=[self parseBrowser:browser accepting:braccept];
		[self processUrl:url /* for:&bi*/ ];
		NSAssert(httpmsg, @"processUrl should set httpmsg");
		NSData *r;
		r=(NSData *)CFHTTPMessageCopySerializedMessage(httpmsg);
		[soc writeData:r];
		CFRelease(httpmsg); httpmsg=NULL;
		[r release];
		[method release];
		[browser release];
		[braccept release];
		[soc closeFile];
	} else {
		[soc readInBackgroundAndNotify];

		if (0) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataReceived:) 
							     name:NSFileHandleReadCompletionNotification 
							   object:[aNotification object]];
	}
}

#if 0

- (browser_info_t) parseBrowser:(NSString *)browser accepting:(NSString *)braccept
{
	browser_info_t bi;
	NSRange r;
	memset(&bi, 0, sizeof(bi));
	bi.bt=bt_http;
	bi.peer=peer; // no retain/release here
	if (!browser) goto unknown_http;
	r=[browser rangeOfString:@"freebox" options: (NSCaseInsensitiveSearch | NSLiteralSearch)];
	if (r.length>0) {
		bi.bt=bt_freebox;
	}
	r=[braccept rangeOfString:@"*/*" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
	if (r.length>0) {
		bi.bt=bt_http;
	}
	r=[braccept rangeOfString:@"application/vnd.wap" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
	if (r.length>0) {
		bi.bt=bt_wap;
	}
	return bi;
unknown_http:
	bi.bt=bt_http;
	return bi;
}
#endif

- (void) processUrl:(NSURL*) url /* for:(browser_info_t *)br */
{
	NSString *repl=nil;
	NSData *reply=nil;
	if (1) NSLog(@"%@\n", 
		       [NSString stringWithFormat:@"url abs=%@ base=%@ host=%@ path=%@ resourceSpecifier=%@ param=%@",
			       [url absoluteString], 
			       [url baseURL], 
			       [url host], 
			       [url path], 
			       [url resourceSpecifier],  
			       [url parameterString]]);
	NSString *p=[url path];
	NSDictionary *dp=[url urlVariables];
	NSArray *ap=[url urlParameters];
	id r;
	r=[delegate contentForPath:p  parameters:ap variables:dp info:self];
	if (r) {
		if ([r isKindOfClass:[NSData class]]) {
			reply=r;
		} else if ([r isKindOfClass:[NSString class]]) {
			repl=r;
		} else {
			NSAssert(0, @"bad class from contentForPath\n");
		}
	}
	if (repl) {
		//printf("found\n");
		reply=[repl dataUsingEncoding:NSUTF8StringEncoding ];
	}
	if (reply) {
		httpmsg=CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200,(CFStringRef) @"OK", kCFHTTPVersion1_1);
#if 0
		if (bt_wap==br->bt) {
			CFHTTPMessageSetHeaderFieldValue(httpmsg,
							 (CFStringRef)@"Content-Type", (CFStringRef)@"text/vnd.wap.wml");
		} else {
			if (!repl) CFHTTPMessageSetHeaderFieldValue(httpmsg,
							 (CFStringRef)@"Content-Type", (CFStringRef)@"image/gif");
		}
#endif
		CFHTTPMessageSetHeaderFieldValue(httpmsg, (CFStringRef)@"Content-Length", 
						 (CFStringRef)[NSString stringWithFormat:@"%d", [reply length]]);
		CFHTTPMessageSetHeaderFieldValue(httpmsg, (CFStringRef)@"Cache-Control", (CFStringRef)@"no-cache, must-revalidate");
		CFHTTPMessageSetHeaderFieldValue(httpmsg, (CFStringRef)@"Pragma", (CFStringRef)@"no-cache");
		
		CFHTTPMessageSetBody(httpmsg, (CFDataRef)reply);
		
	} else {
		printf("not found\n");
		httpmsg=CFHTTPMessageCreateResponse(kCFAllocatorDefault, 404, 
						    (CFStringRef)[NSString stringWithFormat:@"not found : %@", p],
						    kCFHTTPVersion1_1);
		CFHTTPMessageSetBody(httpmsg, (CFDataRef)
				     [[NSString stringWithFormat:@"404 page not found %@<br>", p]dataUsingEncoding:NSUTF8StringEncoding ]);
	}
}

@end

@implementation Httpd

static Httpd *currentHttpd=nil;

- (id) init
{
	return [self initWithPort:8080];
}

- (id) initWithPort: (unsigned short)port;
{
	self = [super init];
	if (self != nil) {
		int s;
		int rc;
		struct sockaddr_in serveraddr;
		//socklen_t namelen = sizeof(serveraddr);
		
		// In order to use NSFileHandle's acceptConnectionInBackgroundAndNotify method, we need to create a file descriptor that is itself a socket, bind that socket, and then set it up for listening. At this point, it's ready to be handed off to acceptConnectionInBackgroundAndNotify.
		s = socket(PF_INET, SOCK_STREAM, 0);
		if (s<0) return nil;
		
		memset(&serveraddr, 0, sizeof(serveraddr));
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serveraddr.sin_port = htons(port);
		
		int one=1;
		rc=setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
		if (rc) perror("setsockopt");
		rc=bind(s, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (rc<0) {
			perror("bind");
			close(s);
			return nil;
		}
		rc=listen(s, 5);
		if (rc<0) {
			perror("listen");
			return nil;
		}
		
		httpdListen = [[NSFileHandle alloc] initWithFileDescriptor:s closeOnDealloc:YES];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionReceived:) name:NSFileHandleConnectionAcceptedNotification object:httpdListen];
		[httpdListen acceptConnectionInBackgroundAndNotify];
		
		contextes=[[NSMutableArray arrayWithCapacity:10] retain];
		currentHttpd=self;
	}
	return self;
}

+ (Httpd *) currentHttpd
{
	return currentHttpd;
}

+ (Httpd *) currentHttpdDelegate
{
	return currentHttpd ? [currentHttpd handler] : nil;
}


- (void) dealloc {
	[httpdListen release];
	[contextes release];
	[delegate release];
	[super dealloc];
}

- (void) setDelegate:(id) d
{
	delegate=[d retain];
}
- (id) delegate
{
	return delegate;
}
- (id) handler
{
	if (delegate) return delegate;
	return self;
}

- (void) connectionReceived:(NSNotification *)aNotification 
{
	NSDictionary *ui=[aNotification userInfo];
	NSFileHandle * newsoc = [ui objectForKey:NSFileHandleNotificationFileHandleItem];
	[[aNotification object] acceptConnectionInBackgroundAndNotify];
	int fd=[newsoc fileDescriptor];
	int rc;
	struct sockaddr_in s;
	unsigned int l=sizeof(s);
	rc=getpeername(fd, (struct sockaddr *) &s,&l);
	if (AF_INET != s.sin_family) goto badaddr;
	unsigned long a=htonl(s.sin_addr.s_addr);
	NSString *peer=[NSString stringWithFormat:@"%d.%d.%d.%d",
		(a & 0xFF000000)>>24,
		(a & 0x00FF0000)>>16,
		(a & 0x0000FF00)>>8,
		(a & 0x000000FF)>>0
	];
	
	[newsoc readInBackgroundAndNotify];
	HttpdConnnection *ctx=[[HttpdConnnection alloc]initWithFileHandle:newsoc delegate:[self handler]
							     peer:peer peerPort:htons(s.sin_port)];
	[[NSNotificationCenter defaultCenter] addObserver:ctx selector:@selector(dataReceived:) name:NSFileHandleReadCompletionNotification object:newsoc];
	[contextes addObject:ctx];
	return;
badaddr:
	[newsoc release];
}


- (id) contentForPath:(NSString *)s  parameters:(NSArray *)ap variables:(NSDictionary *)p  info:(id <HttpdInformation>) br
{
	if ([s isEqualToString:@"/copyright.html"]) {
		return @"<title>copyright page</title>"
			"<center>(c) Daniel Braun 2006-2007</center>"
			"</body></html>";
	} 
	return nil;
}



@end
